package com.epam.newsmanagement.database.dbcp;

import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.Map;
import java.util.Properties;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.Semaphore;

import org.apache.log4j.Logger;

import com.epam.newsmanagement.database.dbcp.exception.PoolException;


public final class ConnectionPool {
	private static final Logger logger = Logger.getLogger(ConnectionPool.class);
	private  String driverName;
	private  String url;
	private  String userName;
	private  String password;
	private boolean driverLoaded;
	
	private Semaphore semaphore;
	private Queue<WrappedConnection> availableConnections;
	private Queue<WrappedConnection> connections;
	private int maxActiveConnections;
	private static class InstanceHolder{
		private final static ConnectionPool instance = new ConnectionPool();
	}	
	private ConnectionPool(){
		
	}
	public void init(){
		semaphore = new Semaphore(maxActiveConnections);
		connections = new ArrayBlockingQueue<WrappedConnection>(maxActiveConnections, true);
		availableConnections = new ArrayBlockingQueue<WrappedConnection>(maxActiveConnections, true);
		
	}
	public static ConnectionPool getInstance(){
		return InstanceHolder.instance;
	}
	public WrappedConnection getConnection() throws PoolException{
		WrappedConnection connection = null;
		try{
			semaphore.acquire();
			connection = availableConnections.poll();
			if (connection == null) {
				connection = createConnection();
			}
		}catch(InterruptedException e){
			throw new PoolException(e);
		}
		return connection;
	}
	public void releaseConnections() {
		for(WrappedConnection wrappedConnection : connections){
			try {
				wrappedConnection.connection.close();
			} catch (SQLException e) {
				logger.error(e);
			}
		}
	}
	private WrappedConnection createConnection() throws PoolException {
		if(!driverLoaded){
			try {
				Class.forName(driverName);
			} catch (ClassNotFoundException e) {
				throw new PoolException(e);
			}			
			driverLoaded = true;
		}
		Connection connection = null;
		try {
			connection = DriverManager.getConnection(url, userName, password);
		} catch (SQLException e) {
			throw new PoolException(e);
		}
		WrappedConnection wrappedConnection = new WrappedConnection(connection);
		connections.add(wrappedConnection);
		return wrappedConnection;
	}
	
	private synchronized void returnConnection(WrappedConnection wrappedConnection) {
		availableConnections.add(wrappedConnection);
		semaphore.release();
	}
	
	public String getDriverName() {
		return driverName;
	}

	public void setDriverName(String driverName) {
		this.driverName = driverName;
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public int getMaxActiveConnections() {
		return maxActiveConnections;
	}

	public void setMaxActiveConnections(int maxActiveConnections) {
		this.maxActiveConnections = maxActiveConnections;
	}
	private class WrappedConnection implements Connection{
		private Connection connection;
		
		public WrappedConnection(Connection connection){
			this.connection = connection;
		}
		@Override
		public boolean isWrapperFor(Class<?> iface) throws SQLException {
			return connection.isWrapperFor(iface);
		}

		@Override
		public <T> T unwrap(Class<T> iface) throws SQLException {
			return connection.unwrap(iface);
		}

		@Override
		public void abort(Executor arg0) throws SQLException {
			connection.abort(arg0);
		}

		@Override
		public void clearWarnings() throws SQLException {
			connection.clearWarnings();			
		}

		@Override
		public void close() throws SQLException {
			returnConnection(this);			
		}

		@Override
		public void commit() throws SQLException {
			connection.commit();			
		}

		@Override
		public Array createArrayOf(String arg0, Object[] arg1)
				throws SQLException {
			return connection.createArrayOf(arg0, arg1);
		}

		@Override
		public Blob createBlob() throws SQLException {
			return connection.createBlob();
		}

		@Override
		public Clob createClob() throws SQLException {
			return connection.createClob();
		}

		@Override
		public NClob createNClob() throws SQLException {
			return connection.createNClob();
		}

		@Override
		public SQLXML createSQLXML() throws SQLException {
			return connection.createSQLXML();
		}

		@Override
		public Statement createStatement() throws SQLException {
			return connection.createStatement();
		}

		@Override
		public Statement createStatement(int arg0, int arg1)
				throws SQLException {
			return connection.createStatement(arg0, arg1);
		}

		@Override
		public Statement createStatement(int arg0, int arg1, int arg2)
				throws SQLException {
			return connection.createStatement(arg0, arg1, arg2);
		}

		@Override
		public Struct createStruct(String arg0, Object[] arg1)
				throws SQLException {
			return connection.createStruct(arg0, arg1);
		}

		@Override
		public boolean getAutoCommit() throws SQLException {
			return connection.getAutoCommit();
		}

		@Override
		public String getCatalog() throws SQLException {
			return connection.getCatalog();
		}

		@Override
		public Properties getClientInfo() throws SQLException {
			return connection.getClientInfo();
		}

		@Override
		public String getClientInfo(String arg0) throws SQLException {
			return connection.getClientInfo(arg0);
		}

		@Override
		public int getHoldability() throws SQLException {
			return connection.getHoldability();
		}

		@Override
		public DatabaseMetaData getMetaData() throws SQLException {
			return connection.getMetaData();
		}

		@Override
		public int getNetworkTimeout() throws SQLException {
			return connection.getNetworkTimeout();
		}

		@Override
		public String getSchema() throws SQLException {
			return connection.getSchema();
		}

		@Override
		public int getTransactionIsolation() throws SQLException {
			return connection.getTransactionIsolation();
		}

		@Override
		public Map<String, Class<?>> getTypeMap() throws SQLException {
			return connection.getTypeMap();
		}

		@Override
		public SQLWarning getWarnings() throws SQLException {
			return connection.getWarnings();
		}

		@Override
		public boolean isClosed() throws SQLException {
			return connection.isClosed();
		}

		@Override
		public boolean isReadOnly() throws SQLException {
			return connection.isReadOnly();
		}

		@Override
		public boolean isValid(int arg0) throws SQLException {
			return connection.isValid(arg0);
		}

		@Override
		public String nativeSQL(String arg0) throws SQLException {
			return connection.nativeSQL(arg0);
		}

		@Override
		public CallableStatement prepareCall(String arg0) throws SQLException {
			return connection.prepareCall(arg0);
		}

		@Override
		public CallableStatement prepareCall(String arg0, int arg1, int arg2)
				throws SQLException {
			return connection.prepareCall(arg0, arg1, arg2);
		}

		@Override
		public CallableStatement prepareCall(String arg0, int arg1, int arg2,
				int arg3) throws SQLException {
			return connection.prepareCall(arg0, arg1, arg2, arg3);
		}

		@Override
		public PreparedStatement prepareStatement(String arg0)
				throws SQLException {
			return connection.prepareStatement(arg0);
		}

		@Override
		public PreparedStatement prepareStatement(String arg0, int arg1)
				throws SQLException {
			return connection.prepareStatement(arg0, arg1);
		}

		@Override
		public PreparedStatement prepareStatement(String arg0, int[] arg1)
				throws SQLException {
			return connection.prepareStatement(arg0, arg1);
		}

		@Override
		public PreparedStatement prepareStatement(String arg0, String[] arg1)
				throws SQLException {
			return connection.prepareStatement(arg0, arg1);
		}

		@Override
		public PreparedStatement prepareStatement(String arg0, int arg1,
				int arg2) throws SQLException {
			return connection.prepareStatement(arg0, arg1, arg2);
		}

		@Override
		public PreparedStatement prepareStatement(String arg0, int arg1,
				int arg2, int arg3) throws SQLException {
			return connection.prepareStatement(arg0, arg1, arg2, arg3);
		}

		@Override
		public void releaseSavepoint(Savepoint arg0) throws SQLException {
			connection.releaseSavepoint(arg0);
		}

		@Override
		public void rollback() throws SQLException {
			connection.rollback();
		}

		@Override
		public void rollback(Savepoint arg0) throws SQLException {
			connection.rollback(arg0);			
		}

		@Override
		public void setAutoCommit(boolean arg0) throws SQLException {
			connection.setAutoCommit(arg0);			
		}

		@Override
		public void setCatalog(String arg0) throws SQLException {
			connection.setCatalog(arg0);			
		}

		@Override
		public void setClientInfo(Properties arg0)
				throws SQLClientInfoException {
			connection.setClientInfo(arg0);			
		}

		@Override
		public void setClientInfo(String arg0, String arg1)
				throws SQLClientInfoException {
			connection.setClientInfo(arg0, arg1);			
		}

		@Override
		public void setHoldability(int arg0) throws SQLException {
			connection.setHoldability(arg0);			
		}
		
		@Override
		public void setNetworkTimeout(Executor arg0, int arg1)
				throws SQLException {
			connection.setNetworkTimeout(arg0, arg1);
		}

		@Override
		public void setReadOnly(boolean arg0) throws SQLException {
			connection.setReadOnly(arg0);			
		}

		@Override
		public Savepoint setSavepoint() throws SQLException {
			return connection.setSavepoint();
		}

		@Override
		public Savepoint setSavepoint(String arg0) throws SQLException {
			return connection.setSavepoint(arg0);
		}

		@Override
		public void setSchema(String arg0) throws SQLException {
			connection.setSchema(arg0);
		}

		@Override
		public void setTransactionIsolation(int arg0) throws SQLException {
			connection.setTransactionIsolation(arg0);
			
		}

		@Override
		public void setTypeMap(Map<String, Class<?>> arg0) throws SQLException {
			connection.setTypeMap(arg0);
		}
		
	}
}
